﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Serialization;

using vJine.Core.IO.Bin;
using vJine.Core.IO.Xml;

namespace vJine.Lua.GUI.UI {
    public class AppFlow : Canvas, ICommand {
        #region ICommand 成员

        public bool CanExecute(object parameter) {
            string cmd = parameter as string;
            switch (cmd) {
                case "CMD_ALIGN_LEFT":
                    return this.S.is_align_left();
                case "CMD_ALIGN_RIGHT":
                    return this.S.is_align_right();
                case "CMD_ALIGN_HCENTER":
                    return this.S.is_align_hcenter();
                case "CMD_ALIGN_TOP":
                    return this.S.is_align_top();
                case "CMD_ALIGN_BOTTOM":
                    return this.S.is_align_bottom();
                case "CMD_ALIGN_VCENTER":
                    return this.S.is_align_vcenter();
                /**/
                case "CMD_SAVE":
                    return this.S.is_sortable();
                case "CMD_LOAD":
                    return !this.S.is_sortable();
                /**/
                case "CMD_SORT_GRAPH":
                    return this.S.is_sortable();
                case "CMD_RUN":
                    return this.S.is_sortable();
                default:
                    return false;
            }
        }

        public Window winMain { get; set; }
        public void Execute(object parameter) {
            string cmd = parameter as string;
            switch (cmd) {
                case "CMD_ALIGN_LEFT":
                    this.S.align_left();
                    break;
                case "CMD_ALIGN_RIGHT":
                    this.S.align_right();
                    break;
                case "CMD_ALIGN_HCENTER":
                    this.S.align_hcenter();
                    break;
                case "CMD_ALIGN_TOP":
                    this.S.align_top();
                    break;
                case "CMD_ALIGN_BOTTOM":
                    this.S.align_bottom();
                    break;
                case "CMD_ALIGN_VCENTER":
                    this.S.align_vcenter();
                    break;
                /**/
                case "CMD_SAVE":
                    this.S.Save(Environment.CurrentDirectory + "/TC/lua.seq.tsc");
                    break;
                case "CMD_LOAD":
                    this.S.Load(Environment.CurrentDirectory + "/TC/lua.seq.tsc");
                    this.NotifyExcutableChanged();
                    break;
                /**/
                case "CMD_SORT_GRAPH":
                    this.S.sort_graph();
                    break;
                /**/
                case "CMD_RUN":
                    this.S.run(winMain);
                    break;
                default:
                    break;
            }
        }

        public event EventHandler CanExecuteChanged;
        internal void NotifyExcutableChanged() {
            if (this.CanExecuteChanged != null) {
                this.CanExecuteChanged(this, null);
            }
        }

        #endregion

        public void doDrag(NodeConfig nConfig) {
            DragDrop.DoDragDrop(this, nConfig, DragDropEffects.Copy);
        }

        MySelector S = null;
        public LuaContext luaContext { get; set; }
        public NodeConfig nodeConfigs { get; set; }
        public NodeConfig runConfigs { get; set; }

        public AppFlow() {
            this.ClipToBounds = true;
            this.AllowDrop = true;
            this.Focusable = true;

            this.S = new MySelector(this);

            this.DragEnter += (object sender, DragEventArgs e) => {
                NodeConfig nConfig = 
                    e.Data.GetData(typeof(NodeConfig)) as NodeConfig;
                if (nConfig == null) {
                    e.Effects = DragDropEffects.None;;
                } else {
                    e.Effects = DragDropEffects.Copy;
                }
            };

            this.Drop += (object sender, DragEventArgs e) => {
                NodeConfig nConfig = 
                    e.Data.GetData(typeof(NodeConfig)) as NodeConfig;
                if (nConfig == null) {
                    e.Effects = DragDropEffects.None;
                    return;
                }

                Point pXY = e.GetPosition(this);
                this.S.Add(nConfig, pXY.X, pXY.Y);

                this.NotifyExcutableChanged();
            };

            this.MouseUp += (object sender, MouseButtonEventArgs e) => {
                this.NotifyExcutableChanged();
            };
        }

        public void set_context() {
            this.S.luaContext = this.luaContext;
            this.S.nConfig = this.nodeConfigs;
        }
    }

    class MyRender {
        public MyRender() {
            this.N = new List<MyRender>();
        }

        public string Name { get; set; }

        public double X {
            get {
                return Canvas.GetLeft(this.render);
            }
            set {
                Canvas.SetLeft(this.render, value);
            }
        }

        public double Y {
            get {
                return Canvas.GetTop(this.render);
            }
            set {
                Canvas.SetTop(this.render, value);
            }
        }

        public NodeConfig Exec { get; set; }

        public List<MyRender> N { get; set; }

        internal int parents { get; set; }
        internal int counts { get; set; }

        /// <summary>
        /// 拖动时的起点
        /// </summary>
        internal Point Start { get; set; }

        internal Control render { get; set; }

        internal bool Add(MyRender my) {
            if (this.N.Contains(my)) {
                return false;
            }

            this.N.Add(my); my.parents += 1;
            return true;
        }

        internal bool Remove(MyRender my) {
            if (!this.N.Contains(my)) {
                return false;
            }

            this.N.Remove(my); my.parents -= 1;
            return true;
        }

        internal bool is_sorted { get; set; }

        internal List<MyRender> sort() {
            List<MyRender> sorted = new List<MyRender>();
            sorted.Add(this);
            for (int i = 0, len = this.N.Count; i < len; i++) {
                this.N[i].counts += 1;
            }
            sorted.AddRange(this.sort(this.N));
            return sorted;
        }

        internal List<MyRender> sort(List<MyRender> next) {
            List<MyRender> sorted = new List<MyRender>();
            if (next == null || next.Count == 0) {
                return sorted;
            }

            for (int i = 0, len = next.Count; i < len; i++) {
                MyRender myRender_i = next[i];
                if (myRender_i.is_sorted) {
                    continue;
                }


                if (myRender_i.counts >= myRender_i.parents) {
                    myRender_i.is_sorted = true;
                    sorted.Add(myRender_i);
                }
            }

            List<MyRender> unsorted = new List<MyRender>();
            for (int i = 0, len = next.Count; i < len; i++) {
                MyRender myRender_i = next[i];
                if (!myRender_i.is_sorted && !unsorted.Contains(myRender_i)) {
                    unsorted.Add(myRender_i);
                    continue;
                }
                if (myRender_i.N == null || myRender_i.N.Count == 0) {
                    continue;
                }
                for (int j = 0, len_j = myRender_i.N.Count; j < len_j; j++) {
                    MyRender myRender_j = myRender_i.N[j];
                    if (myRender_j.is_sorted) {
                        continue;
                    }

                    if (myRender_i.is_sorted) {
                        myRender_j.counts += 1;
                    }
                    
                    if (unsorted.Contains(myRender_j)) {
                        continue;
                    }
                    unsorted.Add(myRender_j);
                }
            }
            sorted.AddRange(this.sort(unsorted));

            return sorted;
        }

        public override string ToString() {
            return string.Format("Name:{0},Parents:{1},Counts:{2}", this.Name, this.parents, this.counts);
        }
    }

    class MyConnector {
        static readonly Brush lineColor = new SolidColorBrush(Colors.Blue);
        static readonly Brush lineActiveColor = new SolidColorBrush(Colors.YellowGreen);

        bool _IsActive = false;
        public bool IsActive {
            get {
                return this._IsActive;
            }
            set {
                if (value == this._IsActive) {
                    return;
                }
                this._IsActive = value;
                this.P.Stroke = value ? MyConnector.lineActiveColor : MyConnector.lineColor;
                this.P.InvalidateVisual();
            }
        }

        public Canvas Container { get; private set; }
        public MyConnector(Canvas container, Control from, Control to) {
            
            this.Container = container;
            this.FromEnd = from;
            this.ToEnd = to;

            this.P = new System.Windows.Shapes.Path();
            this.P.Stroke = MyConnector.lineColor;
            this.P.StrokeThickness = 1.2;
            
            this.P.MouseLeftButtonDown += (object sender, MouseButtonEventArgs e) => {
                this.IsActive = !this.IsActive;
            };

            PathGeometry pg = new PathGeometry();
            this.J1 = new PathFigure();
            PathSegmentCollection psc = new PathSegmentCollection();

            this.J3 = new BezierSegment() { IsSmoothJoin = true };
            psc.Add(this.J3);
            J1.Segments = psc;

            this.Arrow = new PathFigure();
            PathSegmentCollection pscArrow = new PathSegmentCollection();
            this.A3 = new LineSegment();
            pscArrow.Add(this.A3);
            this.A5 = new LineSegment();
            pscArrow.Add(this.A5);
            this.Arrow.Segments = pscArrow;
            
            pg.Figures = new PathFigureCollection() { this.J1, this.Arrow };
            this.P.Data = pg;

            container.Children.Add(this.P);

            /*更新连接线*/
            from.LayoutUpdated += (object sender, EventArgs e) => {
                this.Refresh();
            };
            /*删除连接线*/
            from.Unloaded += (object sender, RoutedEventArgs e) => {
                if (container.Children.Contains(this.P)) {
                    container.Children.Remove(this.P);
                }
            };
            to.Unloaded += (object sender, RoutedEventArgs e) => {
                if (container.Children.Contains(this.P)) {
                    container.Children.Remove(this.P);
                }
            };
        }

        public Control FromEnd { get; private set; }
        public Control ToEnd { get; private set; }
        public int FromPoint { get; set; }
        public int ToPoint { get; set; }
        public System.Windows.Shapes.Path P { get; set; }


        PathFigure J1 { get; set; }
        BezierSegment J3 { get; set; }

        PathFigure Arrow { get; set; }
        LineSegment A3 { get; set; }
        LineSegment A5 { get; set; }

        public void Refresh() {
            double FX1 = 0, FY1 = 0, FX2 = 0, FY2 = 0;
            double TX1 = 0, TY1 = 0, TX2 = 0, TY2 = 0;

            FX1 = Canvas.GetLeft(this.FromEnd);
            FY1 = Canvas.GetTop(this.FromEnd);
            FX2 = FX1 + this.FromEnd.Width;
            FY2 = FY1 + this.FromEnd.Height;

            TX1 = Canvas.GetLeft(this.ToEnd);
            TY1 = Canvas.GetTop(this.ToEnd);
            TX2 = TX1 + this.ToEnd.Width;
            TY2 = TY1 + this.ToEnd.Height;

            Point[] P = this.getPoints(FX1, FY1, FX2, FY2, TX1, TY1, TX2, TY2);

            this.J1.StartPoint = P[0];
            this.J3.Point1 = P[1];
            this.J3.Point2 = P[2];
            this.J3.Point3 = P[3];

            this.Arrow.StartPoint = P[4];
            this.A3.Point = P[3];
            this.A5.Point = P[5];

        }

        public void Remove() {
            this.Container.Children.Remove(this.P);
        }

        Point[] getPoints(
        double FX1, double FY1, double FX2, double FY2,
        double TX1, double TY1, double TX2, double TY2) {
            int from = 1; int to = 1;
            if (FX1 > TX2) {
                from = 1;
                if (FY1 > TY2) {
                    to = 4;
                } else if (FY2 < TY1) {
                    to = 2;
                } else {
                    to = 3;
                }
            } else if (FY1 > TY2) {
                from = 2;
                if (FX1 > TX2) {
                    to = 3;
                } else if (FX2 < TX1) {
                    to = 1;
                } else {
                    to = 4;
                }
            } else if (FX2 < TX1) {
                from = 3;
                if (FY1 > TY2) {
                    to = 4;
                } else if (FY2 < TY1) {
                    to = 2;
                } else {
                    to = 1;
                }
            } else if (FY2 < TY1) {
                from = 4;
                if (FX1 > TX2) {
                    to = 3;
                } else if (FX2 < TX1) {
                    to = 1;
                } else {
                    to = 2;
                }
            }

            from = this.FromPoint == 0 ? from : this.FromPoint;
            to = this.ToPoint == 0 ? to : this.ToPoint;

            return this.getPoints(
                from, to,
                FX1, FY1, FX2, FY2, TX1, TY1, TX2, TY2);
        }

        Point[] getPoints(int from, int to,
        double FX1, double FY1, double FX2, double FY2,
        double TX1, double TY1, double TX2, double TY2) {
            double offset = 20;
            Point[] P = new Point[6];
            switch (from) {
                case 1:
                    P[0] = new Point(FX1, (FY1 + FY2) / 2);
                    P[1] = new Point(FX1 - offset, (FY1 + FY2) / 2);
                    break;
                case 2:
                    P[0] = new Point((FX1 + FX2) / 2, FY1);
                    P[1] = new Point((FX1 + FX2) / 2, FY1 - offset);
                    break;
                case 3:
                    P[0] = new Point(FX2, (FY1 + FY2) / 2);
                    P[1] = new Point(FX2 + offset, (FY1 + FY2) / 2);
                    break;
                case 4:
                    P[0] = new Point((FX1 + FX2) / 2, FY2);
                    P[1] = new Point((FX1 + FX2) / 2, FY2 + offset);
                    break;
                default:
                    break;
            }

            double arr_len = 5;
            switch (to) {
                case 1:
                    P[2] = new Point(TX1 - offset, (TY1 + TY2) / 2);
                    P[3] = new Point(TX1, (TY1 + TY2) / 2);

                    P[4] = new Point(TX1 - arr_len, (TY1 + TY2) / 2 + arr_len);
                    P[5] = new Point(TX1 - arr_len, (TY1 + TY2) / 2 - arr_len);
                    break;
                case 2:
                    P[2] = new Point((TX1 + TX2) / 2, TY1 - offset);
                    P[3] = new Point((TX1 + TX2) / 2, TY1);

                    P[4] = new Point((TX1 + TX2) / 2 - arr_len, TY1 - arr_len);
                    P[5] = new Point((TX1 + TX2) / 2 + arr_len, TY1 - arr_len);
                    break;
                case 3:
                    P[2] = new Point(TX2 + offset, (TY1 + TY2) / 2);
                    P[3] = new Point(TX2, (TY1 + TY2) / 2);

                    P[4] = new Point(TX2 + arr_len, (TY1 + TY2) / 2 - arr_len);
                    P[5] = new Point(TX2 + arr_len, (TY1 + TY2) / 2 + arr_len);
                    break;
                case 4:
                    P[2] = new Point((TX1 + TX2) / 2, TY2 + offset);
                    P[3] = new Point((TX1 + TX2) / 2, TY2);

                    P[4] = new Point((TX1 + TX2) / 2 + arr_len, TY2 + arr_len);
                    P[5] = new Point((TX1 + TX2) / 2 - arr_len, TY2 + arr_len);
                    break;
                default:
                    break;
            }

            return P;
        }
    }

    class MySelector {
        public MySelector(AppFlow container) {
            this.Container = container;
            this.defaultCursor = this.Container.Cursor;

            this.Indicator =
                new Rectangle() {
                    IsEnabled = false, Visibility = Visibility.Hidden, Height = 0,
                    Fill = new SolidColorBrush(Colors.LightBlue) { Opacity = 0.1 },
                    Stroke = new SolidColorBrush(Colors.Green) { Opacity = 0.3 }, StrokeThickness = 2, StrokeDashArray = { 1.7, 1.7 }
                };
            Canvas.SetZIndex(this.Indicator, 1000);

            this.Container.Children.Add(this.Indicator);
            this.Container.MouseDown += Container_MouseDown;
            this.Container.MouseMove += Container_MouseMove;
            this.Container.MouseUp += Container_MouseUp;
            this.Container.KeyDown += Container_KeyDown;

            this.SLECS = new List<Control>();
            this.CONNS = new List<MyConnector>();

            this.IsEnabled = true;
        }

        public LuaContext luaContext { get; set; }
        public NodeConfig nConfig { get; set; }

        ~MySelector() {
            this.Container.MouseDown -= Container_MouseDown;
            this.Container.MouseMove -= Container_MouseMove;
            this.Container.MouseUp -= Container_MouseUp;
        }

        public bool IsEnabled { get; set; }
        public bool IsSlecting { get; private set; }

        Point Start; Point Final;
        Control START = null;
        List<Point> STARTS = new List<Point>();

        AppFlow Container;
        Rectangle Indicator;
        List<Control> RENDERS = new List<Control>();
        List<Control> SLECS = new List<Control>();
        List<MyConnector> CONNS = new List<MyConnector>();

        Cursor defaultCursor = null;
        void Container_MouseDown(object sender, MouseButtonEventArgs e) {
            if (!this.IsEnabled) {
                return;
            }
            this.Container.CaptureMouse();

            this.Indicator.IsEnabled = true;
            this.Indicator.Visibility = Visibility.Visible;

            this.Start = e.GetPosition(this.Container);
            Control ctrl =
                this.hit_test(this.SLECS, this.Start.X, this.Start.Y);
            if (ctrl == null) {
                ctrl =
                this.hit_test(this.RENDERS, this.Start.X, this.Start.Y);
                if (ctrl == null) { //选择
                    this.IsSlecting = true;
                    this.START = null;
                    this.Container.Cursor = Cursors.Cross;
                } else if (this.RENDERS.Count >= 2) { //连接
                    this.START = ctrl;
                    this.IsSlecting = false;
                    this.Container.Cursor = Cursors.Hand;
                }
            } else if (this.SLECS.Contains(ctrl)) { //拖动
                this.IsSlecting = false;
                this.START = null;
                this.Container.Cursor = Cursors.ScrollAll;
            }
        }

        void Container_MouseMove(object sender, MouseEventArgs e) {
            try {
                if (!this.IsEnabled) {
                    return;
                }
                if (!this.Indicator.IsEnabled) {
                    return;
                }

                this.Final = e.GetPosition(this.Container);
                if (this.IsSlecting) { //选择
                    Canvas.SetLeft(this.Indicator, Math.Min(this.Start.X, this.Final.X));
                    Canvas.SetTop(this.Indicator, Math.Min(this.Start.Y, this.Final.Y));
                    this.Indicator.Width = Math.Abs(this.Start.X - this.Final.X);
                    this.Indicator.Height = Math.Abs(this.Start.Y - this.Final.Y);
                    this.Indicator.InvalidateVisual();
                } else if (this.START != null) { //连接

                } else { //拖动
                    if (this.SLECS.Count > 0) {
                        double offset_x = this.Final.X - this.Start.X;
                        double offset_y = this.Final.Y - this.Start.Y;
                        for (int i = 0, len = this.SLECS.Count; i < len; i++) {
                            Control ctrl_i = this.SLECS[i];
                            Point start_i = this.STARTS[i];
                            Canvas.SetLeft(ctrl_i, start_i.X + offset_x);
                            Canvas.SetTop(ctrl_i, start_i.Y + offset_y);
                        }
                    }
                }
            } finally {
                this.Container.NotifyExcutableChanged();
            }
        }

        Brush normalBrush = new SolidColorBrush(Colors.Green);
        Brush activeBrush = new SolidColorBrush(Colors.Yellow);
        void Container_MouseUp(object sender, MouseButtonEventArgs e) {
            try {
                if (!this.IsEnabled) {
                    return;
                }
                if (!this.Indicator.IsEnabled) {
                    return;
                }
                this.Indicator.IsEnabled = false;
                this.Indicator.Visibility = Visibility.Hidden;
                this.Indicator.Height = 0;

                this.Container.ReleaseMouseCapture();
                {
                    this.Container.IsEnabled = true;
                    this.Container.Focusable = true;
                    this.Container.Focus();
                }

                if (this.IsSlecting) { //选择
                    this.hit_test(this.SLECS,
                        Math.Min(this.Start.X, this.Final.X),
                        Math.Min(this.Start.Y, this.Final.Y),
                        Math.Max(this.Start.X, this.Final.X),
                        Math.Max(this.Start.Y, this.Final.Y)
                        );

                    if (this.SLECS.Count > 0) {
                        for (int i = 0, len = this.SLECS.Count; i < len; i++) {
                            this.SLECS[i].BorderBrush = this.activeBrush;
                        }
                    } else {
                        for (int i = 0, len = this.Container.Children.Count; i < len; i++) {
                            this.Container.Children[i].SetValue(Control.BorderBrushProperty, this.normalBrush);
                        }
                    }
                } else if (this.START != null) { //连接
                    Point pend = e.GetPosition(this.Container);
                    Control ctrl = this.hit_test(this.RENDERS, pend.X, pend.Y);
                    if (ctrl != null && ctrl != this.START) {
                        this.add_graph(this.START, ctrl);
                    }
                } else { //拖动

                }

                this.STARTS.Clear();
                for (int i = 0, len = this.SLECS.Count; i < len; i++) {
                    Control ctrl_i = this.SLECS[i];
                    this.STARTS.Add(
                        new Point(Canvas.GetLeft(ctrl_i), Canvas.GetTop(ctrl_i))
                        );
                }
            } finally {
                this.Container.Cursor = this.defaultCursor;
                this.Container.NotifyExcutableChanged();
            }
        }

        void Container_KeyDown(object sender, KeyEventArgs e) {
            try {
                Action<int> set_anchor = (int anchor) => {
                    for (int i = 0, len = this.SLECS.Count; i < len; i++) {
                        Control select_i = this.SLECS[i];

                        for (int c = 0; c < this.CONNS.Count; c++) {
                            MyConnector conn_c = this.CONNS[c];
                            if (!conn_c.IsActive) {
                                continue;
                            }

                            if (conn_c.FromEnd == select_i) {
                                conn_c.FromPoint = anchor;
                            } else if (conn_c.ToEnd == select_i) {
                                conn_c.ToPoint = anchor;
                            } else {
                                continue;
                            }
                            conn_c.Refresh();
                        }
                    }
                };

                switch (e.Key) {
                    case Key.Escape:
                        this.SLECS.Clear();
                        for (int i = 0, len = this.Container.Children.Count; i < len; i++) {
                            this.Container.Children[i].SetValue(Control.BorderBrushProperty, this.normalBrush);
                        }
                        for (int i = 0, len = this.CONNS.Count; i < len; i++) {
                            this.CONNS[i].IsActive = false;
                        }
                        break;
                    case Key.Delete:
                        while (this.SLECS.Count > 0) {
                            Control select_i = this.SLECS[0];
                            for (int c = 0; c < this.CONNS.Count; c++) {
                                MyConnector conn_i = this.CONNS[c];
                                if (conn_i.FromEnd == select_i || conn_i.ToEnd == select_i) {
                                    conn_i.IsActive = true;
                                }
                            }
                            this.SLECS.Remove(select_i);
                            this.Remove(select_i);
                        }
                        for (int i = 0; i < this.CONNS.Count; ) {
                            if (this.CONNS[i].IsActive) {
                                MyConnector conn_i = this.CONNS[i];

                                this.remove_graph(conn_i);
                            } else {
                                i++;
                            }
                        }
                        break;
                    case Key.D0:
                        set_anchor(0);
                        break;
                    case Key.D1:
                        set_anchor(1);
                        break;
                    case Key.D2:
                        set_anchor(2);
                        break;
                    case Key.D3:
                        set_anchor(3);
                        break;
                    case Key.D4:
                        set_anchor(4);
                        break;

                    default:
                        break;
                }
            } finally {
                this.Container.NotifyExcutableChanged();
            }
        }

        Control hit_test(List<Control> lists, double X, double Y) {
            for (int i = 0, len = lists.Count; i < len; i++) {
                Control ctrl_i = lists[i];
                double X1 = Canvas.GetLeft(ctrl_i);
                double Y1 = Canvas.GetTop(ctrl_i);
                double X2 = X1 + ctrl_i.Width;
                double Y2 = Y1 + ctrl_i.Height;

                if (X >= X1 && X <= X2 && Y >= Y1 && Y <= Y2) {
                    return ctrl_i;
                }
            }
            return null;
        }

        void hit_test(List<Control> lists, double X1, double Y1, double X2, double Y2) {
            int hit_count = 0;
            for (int i = 0, len = this.Container.Children.Count; i < len; i++) {
                Control v_i = this.Container.Children[i] as Control;
                if (v_i == null) {
                    continue;
                }

                double x1 = Canvas.GetLeft(v_i);
                double y1 = Canvas.GetTop(v_i);
                double x2 = x1 + v_i.Width;
                double y2 = y1 + v_i.Height;

                if (x1 >= X1 && x2 <= X2 && y1 >= Y1 && y2 <= Y2) {
                    hit_count += 1;
                    if (!lists.Contains(v_i)) {
                        lists.Add(v_i);
                    }
                }
            }
            if (hit_count == 0) {
                lists.Clear();
            }
        }

        List<Control> sort_controls(List<Control> C, bool byX = true) {
            List<Control> Csorted = new List<Control>();
            for (int i = 0, len = C.Count; i < len; i++) {
                Csorted.Add(C[i]);
            }
            Csorted.Sort((Control c1, Control c2) => {
                if (c1 == null && c2 == null) {
                    return 0;
                } else if (c1 == null) {
                    return -1;
                } else if (c2 == null) {
                    return 1;
                } else {
                    bool is_small =
                        byX ? Canvas.GetLeft(c1) < Canvas.GetLeft(c2) : Canvas.GetTop(c1) < Canvas.GetTop(c2);
                    return is_small ? -1 : 1;
                }
            });

            return Csorted;
        }

        double minX(List<Control> C) {
            double min = Canvas.GetLeft(C[0]);
            for (int i = 1, len = C.Count; i < len; i++) {
                double _value = Canvas.GetLeft(C[i]);
                if (_value < min) {
                    min = _value;
                }
            }
            return min;
        }

        double maxX(List<Control> C) {
            double max = Canvas.GetLeft(C[0]);
            for (int i = 1, len = C.Count; i < len; i++) {
                double _value = Canvas.GetLeft(C[i]);
                if (_value > max) {
                    max = _value;
                }
            }
            return max;
        }

        double minY(List<Control> C) {
            double min = Canvas.GetTop(C[0]);
            for (int i = 1, len = C.Count; i < len; i++) {
                double _value = Canvas.GetTop(C[i]);
                if (_value < min) {
                    min = _value;
                }
            }
            return min;
        }

        double maxY(List<Control> C) {
            double max = Canvas.GetTop(C[0]);
            for (int i = 1, len = C.Count; i < len; i++) {
                double _value = Canvas.GetTop(C[i]);
                if (_value > max) {
                    max = _value;
                }
            }
            return max;
        }

        public bool is_align_left() {
            return this.SLECS.Count > 1;
        }
        public void align_left() {
            List<Control> CsortedX = this.sort_controls(this.SLECS);
            double minX = this.minX(CsortedX);
            for (int i = 0, len = CsortedX.Count; i < len; i++) {
                Canvas.SetLeft(CsortedX[i], minX);
            }
        }

        public bool is_align_right() {
            return this.SLECS.Count > 1;
        }
        public void align_right() {
            List<Control> CsortedX = this.sort_controls(this.SLECS);
            double maxX = this.maxX(this.SLECS);
            for (int i = 0, len = CsortedX.Count; i < len; i++) {
                Canvas.SetLeft(CsortedX[i], maxX);
            }
        }

        public bool is_align_hcenter() {
            return this.SLECS.Count > 2;
        }
        public void align_hcenter() {
            List<Control> CsortedX = this.sort_controls(this.SLECS);
            double rminX = this.minX(CsortedX);
            double rmaxX = this.maxX(CsortedX);
            double hstep = (rmaxX - rminX) / (CsortedX.Count - 1);
            for (int i = 0, len = CsortedX.Count; i < len; i++) {
                Canvas.SetLeft(CsortedX[i], rminX + i * hstep);
            }
        }

        public bool is_align_top() {
            return this.SLECS.Count > 1;
        }
        public void align_top() {
            List<Control> CsortedY = this.sort_controls(this.SLECS, false);
            double minY = this.minY(CsortedY);
            for (int i = 0, len = CsortedY.Count; i < len; i++) {
                Canvas.SetTop(CsortedY[i], minY);
            }
        }

        public bool is_align_bottom() {
            return this.SLECS.Count > 1;
        }
        public void align_bottom() {
            List<Control> CsortedY = this.sort_controls(this.SLECS, false);
            double maxY = this.maxY(CsortedY);
            for (int i = 0, len = CsortedY.Count; i < len; i++) {
                Canvas.SetTop(CsortedY[i], maxY);
            }
        }

        public bool is_align_vcenter() {
            return this.SLECS.Count > 2;
        }

        public void align_vcenter() {
            List<Control> CsortedY = this.sort_controls(this.SLECS, false);
            double rminY = this.minY(CsortedY);
            double rmaxY = this.maxY(CsortedY);
            double vstep = (rmaxY - rminY) / (CsortedY.Count - 1);
            for (int i = 0, len = CsortedY.Count; i < len; i++) {
                Canvas.SetTop(CsortedY[i], rminY + i * vstep);
            }
        }

        public void sort_graph() {
            List<MyRender> myr = this.gen_graph();

            StringBuilder sbGraph = new StringBuilder();
            for (int i = 0, len_max = myr.Count - 1; i <= len_max; i++) {
                sbGraph.Append(myr[i].Name);
                if (i < len_max) {
                    sbGraph.Append(",");
                }
            }

            MessageBox.Show("Graph:" + sbGraph.ToString());
        }

        public bool is_sortable() {
            return this.MyR.Count > 0;
        }

        public MyRender Add(NodeConfig nConfig, double X, double Y, string myr_name = null) {
            Label newVisual = new Label() {
                Content = nConfig.alias, Width = 50, Height = 50, Background = new SolidColorBrush(Colors.Azure), IsEnabled = true,
                ToolTip = nConfig.alias
            };
            newVisual.MouseDoubleClick += (object s, MouseButtonEventArgs e) => {
                e.Handled = true;

                WinOptions.ShowOptions(this.luaContext, nConfig);
            };

            MyRender myR = new MyRender();
            myR.Name = myr_name ?? nConfig.uri + "@" + myR.GetHashCode();
            myR.render = newVisual;
            myR.Exec = nConfig;

            Brush normalBorderBrush = new SolidColorBrush(Colors.Green);
            Brush selectedBorderBrush = new SolidColorBrush(Colors.Yellow);
            Thickness borderThickness = new Thickness(1);

            {
                Canvas.SetLeft(newVisual, X);
                Canvas.SetTop(newVisual, Y);

                newVisual.BorderBrush = normalBorderBrush;
                newVisual.BorderThickness = borderThickness;
            }
            this.Add(myR);

            return myR;
        }

        internal List<MyRender> MyR = new List<MyRender>();
        public void Add(MyRender render) {
            if (this.MyR.Contains(render)) {
                return;
            }
            Control ctrl = render.render;
            if (ctrl == null) {
                return;
            }
            if (this.RENDERS.Contains(ctrl)) {
                return;
            }

            this.RENDERS.Add(ctrl);
            this.Container.Children.Add(ctrl);
            this.MyR.Add(render);
        }

        void add_graph(Control from, Control to) {
            this.CONNS.Add(new MyConnector(this.Container, from, to));
            this.get_myrender(from).Add(this.get_myrender(to));
        }

        public void Remove(Control ctrl) {
            if (ctrl == null) {
                return;
            }
            if (!this.RENDERS.Contains(ctrl)) {
                return;
            }
            this.Container.Children.Remove(ctrl);
            this.RENDERS.Remove(ctrl);
            for (int i = 0, len = this.MyR.Count; i < len; i++) {
                if (this.MyR[i].render == ctrl) {
                    this.MyR.RemoveAt(i); break;
                }
            }
        }

        void remove_graph(MyConnector conn) {
            if (conn == null) {
                return;
            }

            Control from = conn.FromEnd;
            Control to = conn.ToEnd;
            conn.Remove();
            this.CONNS.Remove(conn);
            MyRender myR = this.get_myrender(from);
            if (myR == null) {
                return;
            }
            myR.Remove(this.get_myrender(to));
        }

        void Clear() {
            for (int i = 0; i < this.MyR.Count;) {
                this.Remove(this.MyR[i].render);
            }
        }

        MyRender get_myrender(Control ctrl) {
            for (int i = 0, len = this.MyR.Count; i < len; i++) {
                if (this.MyR[i].render == ctrl) {
                    return this.MyR[i];
                }
            }

            return null;
        }

        List<MyRender> gen_graph() {
            List<MyRender> parents = new List<MyRender>();

            for (int i = 0, len = this.MyR.Count; i < len; i++) {
                MyRender my_render_i = this.MyR[i];
                my_render_i.is_sorted = false;
                my_render_i.counts = 0;
                
                if (my_render_i.parents == 0) {
                    parents.Add(my_render_i);
                }
            }
            if (parents.Count == 0) {
                throw new Exception("未找到开始节点");
            } else if (parents.Count > 1) {
                throw new Exception("起始节点多余一个");
            }

            return parents[0].sort();
        }

        NodeConfig runConfig =
                new NodeConfig(true) { name = "Main", alias = "vJine.Lua", uri = "Main", configs = new NodeConfig() };
        public void run(Window winMain) {
            List<MyRender> myr = this.gen_graph();
            if (myr.Count == 0) {
                return;
            }
            this.runConfig.configs.Clear();

            for (int i = 0, len = myr.Count; i < len; i++) {
                NodeConfig config_i = myr[i].Exec;
                this.runConfig.Add(config_i);

                if (config_i.configs != null && config_i.configs.Count > 0) {
                    for (int j = 0, len_j = config_i.configs.Count; j < len_j; j++) {
                        NodeConfig config_j = config_i.configs[j];
                        if (string.IsNullOrEmpty(config_j.param)) {
                            continue;
                        }
                        this.runConfig.configs.Add(config_j);
                    }
                }
            }

            WinOptions.ShowOptions(this.luaContext, this.runConfig, winMain);
        }

        public void Save(string file_name) {
            MyMainConfig myConfig = new MyMainConfig() { Flow = new List<MyNodeConfig>() };
            myConfig.set_values(this.runConfig, 0, 0, true);

            for (int i = 0, len = this.MyR.Count; i < len; i++) {
                MyRender myr_i = this.MyR[i];
                //节点
                NodeConfig nConfig_i = myr_i.Exec;
                MyNodeConfig mconfig_i = new MyNodeConfig() { name = myr_i.Name };
                mconfig_i.set_values(nConfig_i, myr_i.X, myr_i.Y);
                myConfig.Flow.Add(mconfig_i);

                //子节点
                if (myr_i.N == null || myr_i.N.Count == 0) {
                    continue;
                } else {
                    mconfig_i.uris = new List<string>();
                    for (int j = 0, len_j = myr_i.N.Count - 1; j <= len_j; j++) {
                        mconfig_i.uris.Add(myr_i.N[j].Name);
                    }
                }
            }
            //
            BinHelper<MyMainConfig>.ToString(myConfig, file_name);
        }

        MyRender get_byname(string name) {
            for (int i = 0, len = this.MyR.Count; i < len; i++) {
                if (this.MyR[i].Name == name) {
                    return this.MyR[i];
                }
            }

            return null;
        }

        public void Load(string file_name) {
            this.Clear();

            MyMainConfig myConfig = BinHelper<MyMainConfig>.Parse(file_name);
            myConfig.get_values(this.runConfig, true);

            for (int i = 0, len = myConfig.Flow.Count; i < len; i++) {
                MyNodeConfig myConfig_i = myConfig.Flow[i];
                NodeConfig xConfig = this.nConfig.get(myConfig_i.uri);

                if (xConfig == null) {
                    throw new Exception("节点未找到：" + myConfig_i.uri);
                }
                myConfig_i.get_values(xConfig);
                this.Add(xConfig, myConfig_i.x, myConfig_i.y, myConfig_i.name);
            }

            for (int i = 0, len = myConfig.Flow.Count; i < len; i++) {
                MyNodeConfig myConfig_i = myConfig.Flow[i];
                MyRender myR = this.get_byname(myConfig_i.name);
                if (myR == null) {
                    continue;
                }

                if (myConfig_i.uris != null && myConfig_i.uris.Count > 0) {
                    for (int j = 0, len_j = myConfig_i.uris.Count; j < len_j; j++) {
                        MyRender myr_c = this.get_byname(myConfig_i.uris[j]);
                        if (myr_c == null) {
                            continue;
                        }

                        this.add_graph(myR.render, myr_c.render);
                    }
                }
            }
        }

        #region save and load

        public class MyMainConfig : MyNodeConfig {
            [XmlAttribute]
            public string version { get; set; }
            [XmlAttribute]
            public string author { get; set; }
            [XmlAttribute]
            public DateTime create { get; set; }
            [XmlAttribute]
            public DateTime update { get; set; }
            [XmlAttribute]
            public string remark { get; set; }

            [XmlElement]
            public List<MyNodeConfig> Flow { get; set; }

            public void save(string file) {
                BinHelper<MyMainConfig>.ToString(this, file);
            }

            public void load(string file) {
                BinHelper<MyMainConfig>.Parse(file, this);
            }
        }

        public class MyNodeConfig {
            [XmlAttribute]
            public string name { get; set; }
            [XmlAttribute]
            public string alias { get; set; }
            [XmlAttribute]
            public string uri { get; set; }

            [XmlAttribute]
            public bool disable { get; set; }
            [XmlAttribute]
            public bool is_init { get; set; }
            [XmlAttribute]
            public bool data { get; set; }
            [XmlAttribute]
            public bool log { get; set; }

            [XmlAttribute]
            public int times { get; set; }
            [XmlAttribute]
            public int step { get; set; }
            [XmlAttribute]
            public double delay { get; set; }

            //TODO:权限类别，数据输出类别（执行输出、$更改输出、禁止输出）

            [XmlAttribute]
            public double x { get; set; }
            [XmlAttribute]
            public double y { get; set; }

            [XmlArray]
            public List<string> uris { get; set; }

            [XmlArray]
            public List<MyConfigItem> Items { get; set; }

            public void set_values(NodeConfig nConfig, double x, double y, bool ignore_items = false) {
                if (nConfig == null) {
                    return;
                }
                this.x = x; this.y = y;
                this.alias = nConfig.alias; this.uri = nConfig.uri;

                this.disable = nConfig.disable; this.is_init = nConfig.is_init;
                this.data = nConfig.data; this.log = nConfig.log;

                this.times = nConfig.times; this.step = nConfig.step; this.delay = nConfig.delay;

                if (ignore_items) {
                    return;
                }

                if (nConfig.configs == null || nConfig.configs.Count == 0) {
                    this.Items = null; return;
                } else {
                    NodeConfig configs = nConfig.configs;
                    this.Items = new List<MyConfigItem>();
                    for (int i = 0, len = configs.Count; i < len; i++) {
                        NodeConfig config_i = configs[i];
                        this.Items.Add(new MyConfigItem() {
                            name = config_i.name,
                            param = config_i.param,
                            data = config_i.data,
                            formula = config_i.formula
                        });
                    }
                }
            }

            public void get_values(NodeConfig nConfig, bool ignore_items = false) {
                if (nConfig == null) {
                    return;
                }
                nConfig.name = this.name;
                nConfig.alias = this.alias; nConfig.uri = this.uri;

                nConfig.disable = this.disable; nConfig.is_init = this.is_init;
                nConfig.data = this.data; nConfig.log = this.log;

                nConfig.times = this.times; nConfig.step = this.step; nConfig.delay = this.delay;

                if (this.Items == null || this.Items.Count == 0) {
                    return;
                }
                if (nConfig.configs == null || nConfig.configs.Count == 0) {
                    return;
                }

                if (ignore_items) {
                    return;
                }

                for (int i = 0, len_i = this.Items.Count; i < len_i; i++) {
                    MyConfigItem myc_i = this.Items[i];
                    for (int j = 0, len_j = nConfig.configs.Count; j < len_j; j++) {
                        NodeConfig nConfig_j = nConfig.configs[j];
                        if (nConfig_j.name == myc_i.name) {
                            nConfig_j.param = myc_i.param;
                            nConfig_j.data = myc_i.data;
                            nConfig_j.formula = myc_i.formula;
                            break;
                        }
                    }
                }
            }
        }

        public class MyConfigItem {
            [XmlAttribute]
            public string name { get; set; }
            [XmlAttribute]
            public string param { get; set; }

            [XmlAttribute]
            public string formula { get; set; }

            [XmlAttribute]
            public bool data { get; set; }
        }

        #endregion
    }
}
